Enhanced FSVZO (FSVZO V2) [Algo Alert]//@version=5
indicator("∭ FSVZO V2 [Algo Alert]", precision = 2)

import TradingView/ta/5

li = input.bool(title = 'Show leading Oscillator', defval = true, group = "Leading Oscillator")
shwvzo = input.bool(title = 'Show FSVZO', defval = true, group = "VZO")
shwpvts = input.bool(title = 'Show Pivots', defval = false, group = "VZO")
legacy = input.bool(title = 'Use legacy signal processing',tooltip = "Signals may appear faster but may also be more noisy",  defval = false, group = "VZO")

//======================================================================================================================
//                                                FAST FOURIER TRANSFORM
//======================================================================================================================

//Fast Fourier Transform
import jdehorty/KernelFunctions/2 as kernel

DFT(x, y, Nx, _dir) =>  
    float _arg = 0.0
    float _cos = 0.0
    float _sin = 0.0
    float xArr_i = 0.0
    float yArr_i = 0.0
    xArr = array.new_float(array.size(x))
    yArr = array.new_float(array.size(y))

    for i = 0 to Nx - 1 by 1
        xArr_i := 0.0
        yArr_i := 0.0
        kx = float(i) / float(Nx)
        _arg := -_dir * 2 * math.pi * kx
        for k = 0 to Nx - 1 by 1
            _cos := math.cos(k * _arg)
            _sin := math.sin(k * _arg)
            xArr_i += array.get(x, k) * _cos - array.get(y, k) * _sin
            yArr_i += array.get(x, k) * _sin + array.get(y, k) * _cos
            yArr_i
        array.set(xArr, i, xArr_i)
        array.set(yArr, i, yArr_i)

    if _dir == 1
        for i = 0 to Nx - 1 by 1
            array.set(x, i, array.get(xArr, i) / float(Nx))
            array.set(y, i, array.get(yArr, i) / float(Nx))
    else
        for i = 0 to Nx - 1 by 1
            array.set(x, i, array.get(xArr, i))
            array.set(y, i, array.get(yArr, i))

//======================================================================================================================
//                                                 FOURIER INPUTS
//======================================================================================================================

N = input.int(3,"Fourier Period", group = "Fourier")
xval = input.source(close,"Source",tooltip = "i.e. the source of the discrete Fourier"+
 " transform", group = "Fourier")
// highlighting = input.bool(true,"Highlighting", group = "Fourier")
//smoothing = input.int(10,"Kernel Smoothing", group = "Fourier")

//======================================================================================================================
//                                                 FOURIER CALCULATIONS
//======================================================================================================================

// Fourier transform
x = array.new_float(N, 0.0)
y = array.new_float(N, 0.0)
for i = 0 to N - 1
    array.set(x, i, xval[i])
    array.set(y, i, 0.0)

DFT(x, y, N, 1)

mag = array.new_float(N, 0.0)
for i = 0 to N - 1
    mag_i = math.sqrt(math.pow(array.get(x, i), 2) + math.pow(array.get(y, i), 2))
    array.set(mag, i, mag_i)

dft = array.get(mag,0)
//dfts = kernel.rationalQuadratic(dft,25,1,smoothing)

//======================================================================================================================
//                                                 WHITE NOISE
//======================================================================================================================

//Ehler's Universal Oscillator
//White noise
bandedge = input.int(10, minval=1, title='White Noise Calculation Period', group = "White Noise")
amp = input(80, "White Noise Amplitude", group = "White Noise")
whitenoise = (close - close[2]) / 2
wnshow = input.bool(false, "Show white noise?", group = "White Noise")
a1 = math.pow(-1.414 * 3.14159 / bandedge, 2)
b1 = 2 * a1 * math.cos(1.414 * 180 / bandedge)
c2 = b1
c3 = -a1 * a1
c1 = 1 - c2 - c3
var filt = float(na)
filt := na(filt[1]) ? 0 : c1 * (whitenoise + nz(whitenoise[1])) / 2.0 + c2 * nz(filt[1]) + c3 * nz(filt[2])

//automatic gain control
var peak = float(na)
peak := na(peak[1]) ? 0 : math.abs(filt) > nz(peak[1]) ? math.abs(filt) : 0.991 * nz(peak[1])
universal = filt / peak

scaled_universal = universal * amp
plot(wnshow ? scaled_universal: na, title="Universal Oscillator", color=universal>0?(universal>0?color.new(#ffffff, 60):color.rgb(0, 166, 6)):(universal>0?color.new(#3c84ff, 70):color.new(#40a3f4, 60)), linewidth=2, style = plot.style_columns)

//======================================================================================================================
//                                                  VZO
//======================================================================================================================

//Vzo Calc
smoothvzo = input.bool(true, "Smooth VZO?", group = "VZO")
malen = 2
malenn = input(18, title='Look-Back Length', group = "Leading Oscillator")
lengthV = input(5, title='VZO Length', group = "VZO")
dvol = math.sign(dft - dft[2]) * volume
dvma = ta.wma(dvol, lengthV) //ema
vma = ta.sma(volume, lengthV)
VZO_ = vma != 0 ? 100 * dvma / vma : 0

// Smoothness (Reduce noise)
var emaLength = 1 //input(1, "EMA Length", group = "VZO")
minEmaLength = 1
maxEmaLength = 8 //8

uniL = universal > 0  
vzoL = VZO_ > VZO_[1] 

// Dynamically adjust EMA length based on conditions
if (uniL and vzoL) or (not uniL and not vzoL)
    emaLength := emaLength + 1
else if(uniL and not vzoL) or (not uniL and vzoL)
    emaLength := emaLength -1
 
// Reset EMA length to default if it exceeds the limits
if emaLength > maxEmaLength or emaLength < minEmaLength
    emaLength := 7

if smoothvzo
    VZO_ := ta.ema2(VZO_, emaLength) 
//

//======================================================================================================================
//                                                  PLOTS
//======================================================================================================================

greencon = legacy ? VZO_ > VZO_[1] : (VZO_ > ta.ema(VZO_, malen))
redcon = legacy ? VZO_ < VZO_[1] : (VZO_ < ta.ema(VZO_, malen))
greencrosscon = legacy ? ta.crossover(VZO_,VZO_[1]) : ta.crossover(VZO_,ta.ema(VZO_, malen))
redcrosscon = legacy ? ta.crossover(VZO_[1],VZO_) : ta.crossover(ta.ema(VZO_, malen),VZO_)

vzocolorgreen = #00ffbb
vzocolorred = #ff1100

mid = plot(0, color=color.new(color.white, 50), title = 'Zero Line')

leadosc = (VZO_ - ta.ema(VZO_, malenn))*0.5

lii = plot(li ? leadosc : na, color = color.new(#757575, 90) , title = "Leading Oscillator")
v1 = plot(shwvzo ? VZO_ : na, color = greencon ? vzocolorgreen : vzocolorred, linewidth = 1)
//v2 = plot(VZO_[1], color = color.rgb(0, 0, 0), linewidth = 1)
v2 = plot(shwvzo ? ta.ema(VZO_, 2) : na, display = display.none, linewidth = 1)
v3 = plot(shwvzo ? (VZO_ >= 0 ? (math.min(VZO_, ta.ema(VZO_, malen))) : (math.max(VZO_, ta.ema(VZO_, malen)))) : na, display = display.none)

plotchar(greencrosscon and shwvzo and shwpvts ? VZO_[1] : na, "Bull Mark", char = "o", location = location.absolute, color = vzocolorgreen, size = size.tiny, offset = -1)
plotchar(redcrosscon and shwvzo and shwpvts ? VZO_[1] : na, "Bear Mark", char = "o", location = location.absolute, color = vzocolorred, size = size.tiny, offset = -1)

fill(v1, v2, color = VZO_ > ta.ema(VZO_, malen) ? vzocolorgreen : vzocolorred, title = 'VZO fill')
fill(v3, mid, ta.ema(VZO_, malen) > 0 ? ta.ema(VZO_, malen) : 0, ta.ema(VZO_, malen) > 0 ? 0 : ta.ema(VZO_, malen), ta.ema(VZO_, malen) > 0 ? color.rgb(0, 255, 187, 50) : #00000000, ta.ema(VZO_, malen) > 0 ? #00000000 : color.rgb(255, 17, 0, 50))
fill(lii, mid, color = leadosc > 0 ? color.new(#00ffbb,40) : color.new(#ff1100,40) )
//======================================================================================================================
//                                                  Overbought/Oversold Zones
//======================================================================================================================

maxval = 120
minval = 80

l1 = plot(maxval, display = display.none)
l2 = plot(-maxval, display = display.none)
l3 = plot(minval, display = display.none)
l4 = plot(-minval, display = display.none)

fill(l1, l3, top_value = 120, bottom_value = 80, top_color = color.rgb(255, 17, 0, 50), bottom_color = color.rgb(255, 17, 0, 90))
fill(l2, l4, top_value = -80, bottom_value = -120, top_color = color.rgb(0, 255, 187, 90), bottom_color = color.rgb(0, 255, 187, 50))

plotchar(greencrosscon and VZO_ > 80 ? 125 : na, char = "x", color = color.white, location = location.absolute, size = size.tiny)
plotchar(redcrosscon and VZO_ < -80 ? -125 : na, char = "x", color = color.white, location = location.absolute, size = size.tiny)

plotchar(greencrosscon and VZO_ < -80 ? -125 : na, char = "▲", color = vzocolorgreen, location = location.absolute, size = size.tiny)
plotchar(redcrosscon and VZO_ > 80 ? 125 : na, char = "▼", color = vzocolorred, location = location.absolute, size = size.tiny)

plotBull = input(title="Plot Bullish", defval=true, group = "Divergences")
plotHiddenBull = input(title="Plot Hidden Bullish", defval=false, group = "Divergences")
plotBear = input(title="Plot Bearish", defval=true, group = "Divergences")
plotHiddenBear = input(title="Plot Hidden Bearish", defval=false, group = "Divergences")
bearColor = vzocolorred
bullColor = vzocolorgreen
hiddenBullColor = color.new(vzocolorgreen, 80)
hiddenBearColor = color.new(vzocolorred, 80)
textColor = color.white
noneColor = color.new(color.white, 100)

lbR = input(title="Pivot Lookback Right", defval=1)
lbL = input(title="Pivot Lookback Left", defval=10)

plFound = na(ta.pivotlow(VZO_, lbL, lbR)) ? false : true
phFound = na(ta.pivothigh(VZO_, lbL, lbR)) ? false : true
_inRange(cond) =>
	bars = ta.barssince(cond == true)
	-80 <= bars and bars <= 80

//------------------------------------------------------------------------------
// Regular Bullish
// Osc: Higher Low

oscHL = VZO_[lbR] > ta.valuewhen(plFound, VZO_[lbR], 1) and _inRange(plFound[1])

// Price: Lower Low

priceLL = low[lbR] < ta.valuewhen(plFound, low[lbR], 1)
bullCond = plotBull and priceLL and oscHL and plFound

plot(
     plFound ? VZO_[lbR] : na,
     offset=-lbR,
     title="Regular Bullish",
     linewidth=2,
     color=(bullCond ? bullColor : noneColor)
     )

plotshape(
	 bullCond ? VZO_[lbR] : na,
	 offset=-lbR,
	 title="Regular Bullish Label",
	 text=" Bull ",
	 style=shape.labelup,
	 location=location.absolute,
	 color=bullColor,
	 textcolor=textColor
	 )

//------------------------------------------------------------------------------
// Hidden Bullish
// Osc: Lower Low

oscLL = VZO_[lbR] < ta.valuewhen(plFound, VZO_[lbR], 1) and _inRange(plFound[1])

// Price: Higher Low

priceHL = low[lbR] > ta.valuewhen(plFound, low[lbR], 1)
hiddenBullCond = plotHiddenBull and priceHL and oscLL and plFound

plot(
	 plFound ? VZO_[lbR] : na,
	 offset=-lbR,
	 title="Hidden Bullish",
	 linewidth=2,
	 color=(hiddenBullCond ? hiddenBullColor : noneColor)
	 )

plotshape(
	 hiddenBullCond ? VZO_[lbR] : na,
	 offset=-lbR,
	 title="Hidden Bullish Label",
	 text=" H Bull ",
	 style=shape.labelup,
	 location=location.absolute,
	 color=bullColor,
	 textcolor=textColor
	 )

//------------------------------------------------------------------------------
// Regular Bearish
// Osc: Lower High

oscLH = VZO_[lbR] < ta.valuewhen(phFound, VZO_[lbR], 1) and _inRange(phFound[1])

// Price: Higher High

priceHH = high[lbR] > ta.valuewhen(phFound, high[lbR], 1)

bearCond = plotBear and priceHH and oscLH and phFound

plot(
	 phFound ? VZO_[lbR] : na,
	 offset=-lbR,
	 title="Regular Bearish",
	 linewidth=2,
	 color=(bearCond ? bearColor : noneColor)
	 )

plotshape(
	 bearCond ? VZO_[lbR] : na,
	 offset=-lbR,
	 title="Regular Bearish Label",
	 text=" Bear ",
	 style=shape.labeldown,
	 location=location.absolute,
	 color=bearColor,
	 textcolor=textColor
	 )

//------------------------------------------------------------------------------
// Hidden Bearish
// VZO: Higher High

oscHH = VZO_[lbR] > ta.valuewhen(phFound, VZO_[lbR], 1) and _inRange(phFound[1])

// Price: Lower High

priceLH = high[lbR] < ta.valuewhen(phFound, high[lbR], 1)

hiddenBearCond = plotHiddenBear and priceLH and oscHH and phFound

plot(
	 phFound ? VZO_[lbR] : na,
	 offset=-lbR,
	 title="Hidden Bearish",
	 linewidth=2,
	 color=(hiddenBearCond ? hiddenBearColor : noneColor)
	 )

plotshape(
	 hiddenBearCond ? VZO_[lbR] : na,
	 offset=-lbR,
	 title="Hidden Bearish Label",
	 text=" H Bear ",
	 style=shape.labeldown,
	 location=location.absolute,
	 color=bearColor,
	 textcolor=textColor
	 )

alertcondition(ta.crossover(VZO_,ta.ema(VZO_, malen)), title="Uptrend", message="Go Long! {{exchange}}:{{ticker}}")
alertcondition(ta.crossover(ta.ema(VZO_, malen),VZO_), title="Downtrend", message="Go Short! {{exchange}}:{{ticker}}")
alertcondition(ta.crossover(VZO_,ta.ema(VZO_, malen)) and VZO_ < -80, title="Bullish Reversal", message="Bullish Reversal! {{exchange}}:{{ticker}}")
alertcondition(ta.crossunder(VZO_,ta.ema(VZO_, malen)) and VZO_ > 80, title="Bearish Reversal", message="Bearish Reversal! {{exchange}}:{{ticker}}")